package eu.dominicum.ft.pd;

import eu.dominicum.ft.pd.mao.AssociateLocationsWithStageMAO;
import eu.dominicum.ft.pd.mao.AssociateTeamMemberByYerseyNumberMAO;
import eu.dominicum.ft.pd.mao.MakeFantasyStageMAO;
import eu.dominicum.ft.pd.mao.MakeFantasyTourMAO;
import eu.dominicum.ft.pd.mao.MakeParticipantMAO;
import eu.dominicum.ft.pd.mao.validator.AssociateRiderWithTeamMemberMAO;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * A Fantasy Tour.
 *
 * @author Harry Klerks
 * @since 2013M1
 */
public class FantasyTour extends PersistentDomainObject implements Serializable {

    /**
     * A constant value for a curly closing brace.
     */
    private static final char CURLY_CLOSING_BRACE = '}';
    /**
     * The name of this FantasyTour object.
     */
    private String name;
    /**
     * The Tour that this FantasyTour applies to.
     */
    private Tour tour;

    /**
     * The {@link FantasyTourConfiguration} for this {@link FantasyTour} object.
     */
    private FantasyTourConfiguration configuration;

    /**
     * The associated {@link FantasyStage} objects.
     */
    private final List<FantasyStage> fantasyStages = new ArrayList<>();

    /**
     * The associated {@link Participant} objects.
     */
    private final List<Participant> participants = new ArrayList<>();

    /**
     * Protected constructor to satisfy JPA based persistence implementations.
     */
    protected FantasyTour() {
    }

    /**
     * .
     *
     * @return String
     */
    public final String getName() {
        return name;
    }

    @Override
    public final String toString() {

        StringBuilder stringBuilder = new StringBuilder("FantasyTour{");
        stringBuilder.append("id=");
        stringBuilder.append(super.getId());
        stringBuilder.append(", name=");
        stringBuilder.append(this.name);
        stringBuilder.append(CURLY_CLOSING_BRACE);

        return stringBuilder.toString();
    }

    /**
     * Makes a {@link FantasyTour} object.
     *
     * @param mao -
     * @return FantasyTour
     */
    public static FantasyTour make(final MakeFantasyTourMAO mao) {

        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        Set<ConstraintViolation<MakeFantasyTourMAO>> violations = validator.validate(mao);

        if (!violations.isEmpty()) {
            throw new ValidationException((violations.iterator().next().getMessage()));
        }

        FantasyTour newFantasyTour = new FantasyTour();
        newFantasyTour.name = mao.getName();
        Tour newTour = Tour.make(mao.getMakeTourMAO());
        newFantasyTour.tour = newTour;
        FantasyStage newFantasyStage;
        MakeFantasyStageMAO makeFantasyStageMAO = new MakeFantasyStageMAO();
        for (Stage stage : newTour.getStages()) {
            makeFantasyStageMAO.setStage(stage);
            makeFantasyStageMAO.setFantasyTour(newFantasyTour);
            newFantasyStage = FantasyStage.make(makeFantasyStageMAO);
            newFantasyTour.addToFantasyStages(newFantasyStage);
        }
        newFantasyTour.configuration = FantasyTourConfiguration.make(mao.getMakeFantasyTourConfigurationMAO());

        return newFantasyTour;
    }

    /**
     * Adds a {@link FantasyStage} object to the collection.
     *
     * @param newFantasyStage -
     */
    private void addToFantasyStages(final FantasyStage newFantasyStage) {

        this.fantasyStages.add(newFantasyStage);
    }

    /**
     * .
     *
     * @return Tour
     */
    public final Tour getTour() {
        return tour;
    }

    /**
     * .
     *
     * @return FantasyTourConfiguration
     */
    public final FantasyTourConfiguration getConfiguration() {
        return configuration;
    }

    /**
     * .
     *
     * @return List<FantasyStage>
     */
    public final List<FantasyStage> getFantasyStages() {
        return fantasyStages;
    }

    /**
     * .
     *
     * @return List<Participant>
     */
    public final List<Participant> getParticipants() {
        return participants;
    }

    /**
     * Makes a {@link Participant} object and adds it to collection of associated {@link Participant} objects.
     *
     * @param makeParticipantMAO -
     * @return Participant
     */
    public final Participant makeAndAddParticpant(final MakeParticipantMAO makeParticipantMAO) {

        Participant newParticipant = Participant.make(makeParticipantMAO);
        this.addToParticipants(newParticipant);

        return newParticipant;
    }

    /**
     * Adds the given {@link Participant} object to the associated {@link Participant} objects.
     *
     * @param participant -
     */
    private void addToParticipants(final Participant participant) {

        this.participants.add(participant);
    }

    /**
     * Associates the {@link TeamMember} object identified by the given yersey
     * number with the {@link FantasyTeamMember} object identified by the
     * given Fantasy Team Member ID.
     *
     * @param associateTeamMemberByYerseyNumberMAO
     *         -
     */
    public final void associateTeamMemberByYerseyNumber(
            final AssociateTeamMemberByYerseyNumberMAO associateTeamMemberByYerseyNumberMAO) {

        TeamMember targetTeamMember = null;
        for (Team team : this.tour.getTeams()) {
            for (TeamMember teamMember : team.getTeamMembers()) {
                if (teamMember.getYerseyNumber().equals(associateTeamMemberByYerseyNumberMAO.getYerseyNumber())) {
                    targetTeamMember = teamMember;
                }
            }
        }

        if (targetTeamMember == null) {
            throw new IllegalArgumentException("Ongeldig rugnummer");
        }

        for (Participant participant : this.participants) {
            if (participant.getId().equals(associateTeamMemberByYerseyNumberMAO.getParticipantId())) {
                for (FantasyTeamMember fantasyTeamMember : participant.getFantasyTeamMembers()) {
                    if (fantasyTeamMember.getId().equals(associateTeamMemberByYerseyNumberMAO.getFantasyTeamMemberId())) {
                        fantasyTeamMember.changeTeamMember(targetTeamMember);
                    }
                }
            }
        }
    }

    /**
     * Find the {@link Participant} object identified by the given ID and then invoke
     * {@link eu.dominicum.ft.pd.Participant#revealRealName()}.
     *
     * @param participantId -
     */
    public final void revealParticipantRealName(final Long participantId) {

        participantLoop:
        for (Participant participant : this.participants) {
            if (participant.getId() == participantId) {
                participant.revealRealName();
                break participantLoop;
            }
        }
    }

    /**
     * Determine whether or not there are (still) participant names to reveal.
     *
     * @return boolean
     */
    public final boolean isNamesToReveal() {

        for (Participant participant : participants) {
            if (participant.getHideRealName().equals(Boolean.TRUE)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

    /**
     * Associate a rider with a team member.
     *
     * @param associateRiderWithTeamMemberMAO
     *         -
     */
    public final void associateRiderWithTeamMember(final AssociateRiderWithTeamMemberMAO associateRiderWithTeamMemberMAO) {

        for (Team team : this.tour.getTeams()) {
            for (TeamMember teamMember : team.getTeamMembers()) {
                if (teamMember.getId().equals(associateRiderWithTeamMemberMAO.getTeamMemberId())) {
                    teamMember.associateRider(associateRiderWithTeamMemberMAO.getAssociateRiderMAO());
                }
            }
        }
    }

    /**
     * Associate given {@link Location} object(s) with given {@link Stage} object.
     *
     * @param associateLocationsWithStageMAO -
     */
    public final void associateLocationsWithStage(final AssociateLocationsWithStageMAO associateLocationsWithStageMAO) {

        for (Stage stage : this.tour.getStages()) {
            if (stage.getId().equals(associateLocationsWithStageMAO.getStageId())) {
                stage.associateLocations(associateLocationsWithStageMAO);
            }
        }
    }
}
